Fix NMI race with context switch.
authorkaf24@firebug.cl.cam.ac.uk <kaf24@firebug.cl.cam.ac.uk>
Thu, 20 Oct 2005 10:25:55 +0000 (11:25 +0100)
committerkaf24@firebug.cl.cam.ac.uk <kaf24@firebug.cl.cam.ac.uk>
Thu, 20 Oct 2005 10:25:55 +0000 (11:25 +0100)
The machine used to auto reboot if an NMI was received in a critical
time window when context switching domains. There is a small time
window when the GDT may become unmapped (after CR3 is updated and
before setting GDTR with the new GDT during a domain context
switch. If an NMI is received during this time window a triple fault
is triggered causing the machine to auto reboot.

Bug found and original patch proposed by Jose Renato Santos
<jsantos@hpl.hp.com>.

Signed-off-by: Keir Fraser <keir@xensource.com>
xen/arch/x86/domain.c
xen/arch/x86/setup.c

index 25302d0f5fa1c7989f083f014c2afa829d824add..7d61eadfab19c48ef81245b8bb98e890ab183e2e 100644 (file)
@@ -226,11 +226,9 @@ struct vcpu *alloc_vcpu_struct(struct domain *d, unsigned int vcpu_id)
 
     if ( (v->vcpu_id = vcpu_id) != 0 )
     {
-        v->arch.schedule_tail = d->vcpu[0]->arch.schedule_tail;
+        v->arch.schedule_tail  = d->vcpu[0]->arch.schedule_tail;
         v->arch.perdomain_ptes =
             d->arch.mm_perdomain_pt + (vcpu_id << PDPT_VCPU_SHIFT);
-        v->arch.perdomain_ptes[FIRST_RESERVED_GDT_PAGE] =
-            l1e_from_page(virt_to_page(gdt_table), PAGE_HYPERVISOR);
     }
 
     return v;
@@ -256,6 +254,7 @@ void free_perdomain_pt(struct domain *d)
 void arch_do_createdomain(struct vcpu *v)
 {
     struct domain *d = v->domain;
+    int vcpuid;
 
     if ( is_idle_task(d) )
         return;
@@ -275,8 +274,20 @@ void arch_do_createdomain(struct vcpu *v)
     set_pfn_from_mfn(virt_to_phys(d->arch.mm_perdomain_pt) >> PAGE_SHIFT,
             INVALID_M2P_ENTRY);
     v->arch.perdomain_ptes = d->arch.mm_perdomain_pt;
-    v->arch.perdomain_ptes[FIRST_RESERVED_GDT_PAGE] =
-        l1e_from_page(virt_to_page(gdt_table), PAGE_HYPERVISOR);
+
+    /*
+     * Map Xen segments into every VCPU's GDT, irrespective of whether every
+     * VCPU will actually be used. This avoids an NMI race during context
+     * switch: if we take an interrupt after switching CR3 but before switching
+     * GDT, and the old VCPU# is invalid in the new domain, we would otherwise
+     * try to load CS from an invalid table.
+     */
+    for ( vcpuid = 0; vcpuid < MAX_VIRT_CPUS; vcpuid++ )
+    {
+        d->arch.mm_perdomain_pt[
+            (vcpuid << PDPT_VCPU_SHIFT) + FIRST_RESERVED_GDT_PAGE] =
+            l1e_from_page(virt_to_page(gdt_table), PAGE_HYPERVISOR);
+    }
 
     v->arch.guest_vtable  = __linear_l2_table;
     v->arch.shadow_vtable = __shadow_linear_l2_table;
index c6653708a1c8b4b12b3cb18aa731c452c550a5c8..388735fb491ad0f258ec47eefdcdcf81f42f49bc 100644 (file)
@@ -141,6 +141,7 @@ static void __init do_initcalls(void)
 static void __init start_of_day(void)
 {
     int i;
+    unsigned long vgdt;
 
     early_cpu_init();
 
@@ -158,10 +159,17 @@ static void __init start_of_day(void)
 
     arch_do_createdomain(current);
     
-    /* Map default GDT into their final position in the idle page table. */
-    map_pages_to_xen(
-        GDT_VIRT_START(current) + FIRST_RESERVED_GDT_BYTE,
-        virt_to_phys(gdt_table) >> PAGE_SHIFT, 1, PAGE_HYPERVISOR);
+    /*
+     * Map default GDT into its final positions in the idle page table. As
+     * noted in arch_do_createdomain(), we must map for every possible VCPU#.
+     */
+    vgdt = GDT_VIRT_START(current) + FIRST_RESERVED_GDT_BYTE;
+    for ( i = 0; i < MAX_VIRT_CPUS; i++ )
+    {
+        map_pages_to_xen(
+            vgdt, virt_to_phys(gdt_table) >> PAGE_SHIFT, 1, PAGE_HYPERVISOR);
+        vgdt += 1 << PDPT_VCPU_VA_SHIFT;
+    }
 
     find_smp_config();